﻿using System;
using System.Collections.Generic;
using Shell.Build;
using Shell.Build.Lifetime;

namespace Shell
{
    public class DefaultShell : IShell // TODO  change it to public / internal?
    {
        private ILifetimeContainer lifetimeContainer;
        private IBuildPolicyCollection buildPolicyCollection;
        private readonly List<IShellExtension> extensions;
        private IBuildChainMaker<BuildStage> buildChainMaker;

        public DefaultShell()
            : this(null)
        {
            this.Attach(new DefaultBuildersExtension());
        }

        public DefaultShell(DefaultShell parent)
        {
            this.Parent = parent;

            // if this is a child shell, adds it to the lifetimecontainer of its parent so we don't have to worry about this shell been collected by GC
            if (this.Parent != null)
            {
                this.Parent.LifetimeContainer.Add(this);
            }

            this.extensions = new List<IShellExtension>();
            this.buildPolicyCollection = new BuildPolicyCollection();
            this.lifetimeContainer = CreateLifetimeContainer();
            this.buildChainMaker = new BuildChainMaker<BuildStage>();
            this.Attach(new DefaultBahaviorExtension());
        }

        protected DefaultShell Parent { get; private set; }

        public IEnumerable<IShellExtension> Extensions
        {
            get { return this.extensions; }
        }

        public virtual IBuildChainMaker<BuildStage> BuildChainMaker
        {
            get { return this.buildChainMaker; }
        }

        public virtual ILifetimeContainer LifetimeContainer
        {
            get { return this.lifetimeContainer; }
        }

        public virtual IBuildPolicyCollection BuildPolicyCollection
        {
            get { return this.buildPolicyCollection; }
        }

        public event EventHandler<TypeRegisterEventArgs> TypeRegistered;

        public event EventHandler<InstanceRegisterEventArgs> InstanseRegistered;

        protected virtual ILifetimeContainer CreateLifetimeContainer()
        {
            return new LifetimeContainer();
        }

        protected virtual void OnTypeRegistered(object sender, TypeRegisterEventArgs e)
        {
            if (this.TypeRegistered != null)
            {
                this.TypeRegistered(sender, e);
            }
        }

        protected virtual void OnInstanseRegistered(object sender, InstanceRegisterEventArgs e)
        {
            if (this.InstanseRegistered != null)
            {
                this.InstanseRegistered(sender, e);
            }
        }

        protected virtual IBuildChain MakeBuildChain()
        {
            return this.BuildChainMaker.Make();
        }

        public virtual void Register(string name, Type from, Type to, ILifetimePolicy lifetimePolicy)
        {
            if (String.IsNullOrEmpty(name))
            {
                name = null;
            }
            // TODO: here we need check the types
            this.OnTypeRegistered(this, new TypeRegisterEventArgs(name, from, to, lifetimePolicy));
        }

        public virtual void Register(string name, Type from, object instanse, ILifetimePolicy lifetimePolicy)
        {
            if (String.IsNullOrEmpty(name))
            {
                name = null;
            }
            // TODO: here we need check the types
            this.OnInstanseRegistered(this, new InstanceRegisterEventArgs(name, from, instanse, lifetimePolicy));
        }

        public virtual object Resolve(string name, Type targetType)
        {
            var identity = new BuildIdentity(targetType, name);
            IBuildContext context = new BuildContext(this, identity, this.MakeBuildChain(),
                                                     this.LifetimeContainer, this.BuildPolicyCollection);
            return context.BuildChain.Build(context);
        }

        public void Attach(IShellExtension extension)
        {
            lock (this.extensions)
            {
                this.extensions.Add(extension);
            }
            extension.Initialize(new DefaultShellExtensionContext(this));
        }

        public void Detach(IShellExtension extension)
        {
            if (extension != null)
            {
                lock (this.extensions)
                {
                    this.extensions.Remove(extension);
                }
                extension.Dispose();
            }
        }

        public IShell CreateChild()
        {
            return new DefaultShell(this);
        }

        public void Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                while (this.extensions.Count > 0)
                {
                    var lastExtension = this.extensions[this.extensions.Count - 1];
                    lastExtension.Dispose();
                    this.extensions.Remove(lastExtension);
                }
                this.buildChainMaker.Dispose();
                this.buildPolicyCollection.Dispose();
                this.lifetimeContainer.Dispose();
                this.buildChainMaker = null;
                this.buildPolicyCollection = null;
                this.lifetimeContainer = null;
            }
        }
    }
}
